Analisando os dados¶



Autor: Flávio Belizário da Silva Mota

Instituto Nacional de Pesquisas Espaciais (INPE)
Avenida dos Astronautas, 1758, Jardim da Granja, São José dos Campos, SP 12227-010, Brazil

Contato: flavio.belizario.mota@gmail.com
Professor: Rafael Santos


Objetivo. Esse caderno Jupyter tem como objetivo apresentar a análise dos dados para o projeto da disciplina CAP394 - Introdução à Ciência de Dados. Os dados são provenientes do projeto CRISIS NLP e foram coletados utilizando publicações do Twitter referentes à pandemia de COVID-19. O conjunto utilizado para a análise foi coletado e tratado em uma etapa prévia (vide: coleta) e contém informações sobre data de criação de uma publicação, origem da informação geolocalizada do dado e o estado e cidade de origem da publicação.


Os dados podem ser encontrados em:
https://crisisnlp.qcri.org/covid19

Analisando os dados¶


O conjunto de dados¶


O conjunto de dados empregado nas análises a seguir foi elaborado na etapa de coleta e tratamento deste trabalho (vide: coleta). Em resumo, são dados provenientes de publicações do Twitter referentes à pandemia de COVID-19. Especificamente, o conjunto a seguir contém informações sobre a data de criação, fonte das informações geolocalizadas do dado e cidade e estado de publicações com origem no Brasil entre os dias 19 de fevereiro e 20 de março de 2020.

Vamos iniciar nossa análise carregando o conjunto. Bibliotecas necessárias para a análise serão importadas ao longo do caderno.

In [1]:
import pandas as pd
df = pd.read_csv('tweets_covid_brasil.csv')
df
Out[1]:
created_at geo_source estado cidade
0 2020-02-19 16:42:08+00:00 user_location São Paulo São Paulo
1 2020-02-19 19:49:00+00:00 user_location São Paulo São Paulo
2 2020-02-19 18:04:27+00:00 user_location Minas Gerais Belo Horizonte
3 2020-02-19 20:10:17+00:00 user_location São Paulo NaN
4 2020-02-19 18:47:44+00:00 user_location Maranhão NaN
... ... ... ... ...
1863367 2020-03-20 08:44:49+00:00 user_location Rondônia NaN
1863368 2020-03-20 10:57:02+00:00 user_location NaN NaN
1863369 2020-03-20 12:03:33+00:00 user_location NaN NaN
1863370 2020-03-20 09:50:36+00:00 user_location NaN NaN
1863371 2020-03-20 10:57:04+00:00 user_location NaN NaN

1863372 rows × 4 columns

O conjunto possui 1.863.372 registros (ou linhas) e 4 colunas.

Descrevendo o conjunto¶


Vamos visualizar uma descrição básica dos dados:

In [2]:
df.describe()
Out[2]:
created_at geo_source estado cidade
count 1863372 1863372 968815 622949
unique 1083140 3 28 1893
top 2020-03-15 02:21:53+00:00 user_location São Paulo Rio de Janeiro
freq 15 1833373 303951 106331

Através dessa simples descrição já podemos extrair algumas informações interessantes como:

  • A quantidade de registros com valores preenchidos não é a mesma para todos os campos. Enquanto created_at e geo_source possuem uma contagem de linhas iguais ao tamanho do DataFrame, estado e cidade têm menos registros, o que indica a presença de valores nulos.
  • Existem 3 valores únicos para o campo geo_source (o que condiz com o tratamento feito anteriormente), 28 valores únicos para estado e 1893 valores únicos para cidade. Sabemos que no Brasil existem 26 estados, mais o Distrito Federal, então o valor 28 deve ser investigado. Já para as cidades, o valor fica abaixo do número de 5570 municípios que consta na listagem do IBGE.
  • Em relação ao valor mais frequente, é possível dizer que user_location foi a fonte mais comum das informações geolocalizadas desses dados. São Paulo lidera como o estado com maior número de publicações geolocalizadas, mas é a cidade do Rio de Janeiro que detém o maior número de publicações.

Essa descrição não apresenta dados estatísticos como média, mediana, mínimos e máximos (apesar dos campos count e top) porque a biblioteca pandas entendeu que todos os dados são categoricos, o que é verdade para a maioria deles, mas não todos. Vamos abordar a questão dos tipos a seguir.
Podemos analisar quais são os valores únicos das colunas geo_source, estado e cidade.

In [3]:
df['geo_source'].unique()
Out[3]:
array(['user_location', 'place', 'coordinates'], dtype=object)

O campo geo_source contém os valores 'user_location', 'place' e 'coordinates', o que está de acordo com as informações selecionadas no momento da coleta dos dados.

In [4]:
df['estado'].unique()
Out[4]:
array(['São Paulo', 'Minas Gerais', 'Maranhão', nan, 'Rio de Janeiro',
       'Rio Grande do Sul', 'Santa Catarina', 'Pará', 'Bahia', 'Paraíba',
       'Paraná', 'Acre', 'Amazonas', 'Pernambuco', 'Mato Grosso', 'Amapá',
       'Espírito Santo', 'Piauí', 'Ceará', 'Rondônia', 'Goiás',
       'Rio Grande do Norte', 'Mato Grosso do Sul', 'Tocantins',
       'Sergipe', 'Roraima', 'Alagoas', 'Federal District',
       'South Region'], dtype=object)

Aqui podemos ver que o campo estado contém os 26 estados brasileiros, o Distrito Federal (que aqui recebeu o nome de 'Federal District'), valores nulos (representado pelo valor 'nan') e um valor chamado 'South Region'. Consultando a documentação dos dados do Twitter, verificamos que o campo state fornecido pode estar relacionado também com província por conta de outros países e, nesse caso, 'South Region' faz referência à toda região sul do Brasil. Mais adiante, podemos analisar a relação dos registros com esse valor para o estado e tentar correlacionar a cidade com o estado.

In [5]:
df['cidade'].unique()
Out[5]:
array(['São Paulo', 'Belo Horizonte', nan, ..., 'Ipuaçu', 'Nioaque',
       'Conselheiro Mairinck'], dtype=object)

Para o campo cidade, como vimos na descrição acima, a quantidade de valores é muito grande para exibir todos unicamente.

Tipos¶


Vamos analisar os tipos de dados de cada uma das colunas:

In [6]:
df.dtypes
Out[6]:
created_at    object
geo_source    object
estado        object
cidade        object
dtype: object

Nesse ponto, todas as colunas são do tipo object o que, pela documentação da biblioteca pandas, representa um tipo de "objeto arbitrário" que "deve ser evitado na medida do possível para desempenho e interoperabilidade com outras bibliotecas e métodos". Cadeias de caracteres também são entendidas pela biblioteca como sendo desse tipo.

Sendo assim, é adequado que os devidos ajustes nos tipos sejam feitos. No caso do conjunto de dados que estamos analisando, o ajuste a ser feito é na coluna referente à data de criação da publicação, uma vez que se trata de um timestamp. As demais colunas podem ser mantidas como o tipo object por se tratarem de cadeia de caracteres.

Para converter o campo created_at, podemos empregar a função to_datetime da própria biblioteca:

In [7]:
df['created_at'] = pd.to_datetime(df['created_at'])
df.dtypes
Out[7]:
created_at    datetime64[ns, UTC]
geo_source                 object
estado                     object
cidade                     object
dtype: object

Agora temos a coluna com o tipo mais adequado para o atributo created_at.

Verficando a ausência de dados¶


Uma questão importante antes de começarmos a trabalhar com os dados é verificar a quantidade de valores nulos, pois como vimos na seção de descrição dos dados, as colunas estados e cidade têm menos registros que o número total de registros do conjunto de dados. Podemos utilizar o comando a seguir para obter essa contagem:

In [8]:
df.isna().sum()
Out[8]:
created_at          0
geo_source          0
estado         894557
cidade        1240423
dtype: int64

O atributo estado tem 894.557 registro nulos, o que também significa dizer que são 968.815 registros que foram preenchidos. Já cidade tem 1.240.423 registros nulos, ou seja, 622.949 registros preenchidos. Isso se deve ao fato de que essas informaçõe não são obrigatoriamente preenchidas ou retornadas pelo Twitter. Vale lembrar que para a etapa de coleta dos dados, bastava que a publicação tivesse origem no Brasil.
Como não é possível inferir o estado e cidade de origem desses dados para preencher os valores ausentes, vamos criar uma categoria chamada 'N.I' (Não Informado) para esses dados. Podemos usar o código abaixo para realizar esse preenchimento:

In [9]:
df.fillna('N.I.', axis=1, inplace=True)

Podemos realizar a contagem dos valores nulos novamente para verificar se a alteração surtiu efeito:

In [10]:
df.isna().sum()
Out[10]:
created_at    0
geo_source    0
estado        0
cidade        0
dtype: int64

Aplicando algumas transformações¶


Para facilitar a manipulação dos dados nas análises a seguir, vamos realizar pequenas tranformações no dado. Primeiramente, vamos criar um campo sigla baseado no nome do estado.

In [11]:
estados = {
    'Acre':'AC',
    'Alagoas':'AL',
    'Amapá':'AP',
    'Amazonas':'AM',
    'Bahia':'BA',
    'Ceará':'CE',
    'Federal District':'DF',
    'Espírito Santo':'ES',
    'Goiás':'GO',
    'Maranhão':'MA',
    'Mato Grosso':'MT',
    'Mato Grosso do Sul':'MS',
    'Minas Gerais':'MG',
    'Pará':'PA',
    'Paraíba':'PB',
    'Paraná':'PR',
    'Pernambuco':'PE',
    'Piauí':'PI',
    'Rio de Janeiro':'RJ',
    'Rio Grande do Norte':'RN',
    'Rio Grande do Sul':'RS',
    'Rondônia':'RO',
    'Roraima':'RR',
    'Santa Catarina':'SC',
    'São Paulo':'SP',
    'Sergipe':'SE',
    'Tocantins':'TO',
    'N.I.':'N.I'
}

df['sigla']=df['estado'].map(estados)
df
Out[11]:
created_at geo_source estado cidade sigla
0 2020-02-19 16:42:08+00:00 user_location São Paulo São Paulo SP
1 2020-02-19 19:49:00+00:00 user_location São Paulo São Paulo SP
2 2020-02-19 18:04:27+00:00 user_location Minas Gerais Belo Horizonte MG
3 2020-02-19 20:10:17+00:00 user_location São Paulo N.I. SP
4 2020-02-19 18:47:44+00:00 user_location Maranhão N.I. MA
... ... ... ... ... ...
1863367 2020-03-20 08:44:49+00:00 user_location Rondônia N.I. RO
1863368 2020-03-20 10:57:02+00:00 user_location N.I. N.I. N.I
1863369 2020-03-20 12:03:33+00:00 user_location N.I. N.I. N.I
1863370 2020-03-20 09:50:36+00:00 user_location N.I. N.I. N.I
1863371 2020-03-20 10:57:04+00:00 user_location N.I. N.I. N.I

1863372 rows × 5 columns

Vamos também separar a coluna created_at em duas colunas: 'data' e 'horario'.

In [12]:
df['data'] = pd.to_datetime(df['created_at']).dt.date
df['horario'] = pd.to_datetime(df['created_at']).dt.time
df
Out[12]:
created_at geo_source estado cidade sigla data horario
0 2020-02-19 16:42:08+00:00 user_location São Paulo São Paulo SP 2020-02-19 16:42:08
1 2020-02-19 19:49:00+00:00 user_location São Paulo São Paulo SP 2020-02-19 19:49:00
2 2020-02-19 18:04:27+00:00 user_location Minas Gerais Belo Horizonte MG 2020-02-19 18:04:27
3 2020-02-19 20:10:17+00:00 user_location São Paulo N.I. SP 2020-02-19 20:10:17
4 2020-02-19 18:47:44+00:00 user_location Maranhão N.I. MA 2020-02-19 18:47:44
... ... ... ... ... ... ... ...
1863367 2020-03-20 08:44:49+00:00 user_location Rondônia N.I. RO 2020-03-20 08:44:49
1863368 2020-03-20 10:57:02+00:00 user_location N.I. N.I. N.I 2020-03-20 10:57:02
1863369 2020-03-20 12:03:33+00:00 user_location N.I. N.I. N.I 2020-03-20 12:03:33
1863370 2020-03-20 09:50:36+00:00 user_location N.I. N.I. N.I 2020-03-20 09:50:36
1863371 2020-03-20 10:57:04+00:00 user_location N.I. N.I. N.I 2020-03-20 10:57:04

1863372 rows × 7 columns

Anteriormente, percebemos a presença do valor 'South Region' no campo estado. Vamos verificar se nos registros onde esse valor ocorre, existem valores para o campo cidade que permitam criar uma função que associe um estado da região sul ao registro. Podemos fazer isso criando uma máscara que verifica se valor da coluna estado é igual a 'South Region' e se o valor da coluna cidade é diferente de "Não Informado":

In [13]:
mascara_south_region = (df['estado'] == 'South Region') & (df['cidade'] != 'N.I.')
df.loc[mascara_south_region]
Out[13]:
created_at geo_source estado cidade sigla data horario

Como não existe nenhum registro retornado, não é possível determinar quais os estados.

Explorando o dado¶


Após as transformações realizadas, podemos inciar a exploração dos dados. Primeiramente vamos observar a distribuição das publicações ao longo das datas:

In [14]:
import matplotlib.pyplot as plt
df.groupby(df['data']).count()['created_at'].plot(figsize=(15,5), kind="bar", xlabel='Data da publicação', ylabel='Nº de publicações')
Out[14]:
<AxesSubplot:xlabel='Data da publicação', ylabel='Nº de publicações'>

Através desse gráfico conseguimos notar algumas datas na distribuição nas quais o número de publicações cresce consideravelmente. Vamos considerar algumas dessas datas que estão relacionadas a momentos específicos da pandemia do COVID: 26 de fevereiro, 11, 12 e 13 de março e 17 de março de 2020.

  • Em 26/02/2020 o Ministério da Saúde confirmou o primeiro caso de COVID no território brasileiro (G1).
  • No dia 11/03/2020, a OMS declara oficialmente o estado de pandemia global do COVID-19 (G1).
  • Em 12/03/2020 foi publicada pelo Ministério da Saúde uma portaria que definia como deveriam ser feitos o isolamento e a quarentena para enfrentar a pandemia do novo coronavírus (G1).
  • No dia 13/03/2020 diversos eventos públicos são cancelados no país em decorrência das medidas de retrição de circulação de pessoas para contenção do contáigio do vírus (G1).
  • Na tarde de 17/03/2020 o Brasil confirma a primeira morte por COVID no país (CNN).

Podemos também verificar algumas medidas estatísticas como a média diária de publicações, o menor e o maior número de publicações em um dia:

In [15]:
print("A média de publicações diária foi de %.2f" % df.groupby(df['data']).count()['created_at'].mean())
print("O menor nº de publicações foi de %s" % df.groupby(df['data']).count()['created_at'].min())
print("O maior nº de publicações foi de %s" % df.groupby(df['data']).count()['created_at'].max())
A média de publicações diária foi de 60108.77
O menor nº de publicações foi de 4002
O maior nº de publicações foi de 173003

É possível distribuir o número de publicações por estado:

In [16]:
df.loc[df['estado'] != 'N.I.'].groupby(df['sigla']).count()['created_at'].sort_values(ascending=False).plot(figsize=(15,5), kind="bar", xlabel='Estado', ylabel='Nº de publicações')
Out[16]:
<AxesSubplot:xlabel='Estado', ylabel='Nº de publicações'>
In [19]:
!pip install plotly
Requirement already satisfied: plotly in c:\users\flavi\anaconda3\lib\site-packages (5.10.0)
Requirement already satisfied: tenacity>=6.2.0 in c:\users\flavi\anaconda3\lib\site-packages (from plotly) (8.0.1)
In [20]:
import plotly as plt
import plotly.express as px
import json
In [22]:
df_estados_publicacoes = pd.DataFrame(df.loc[df['estado'] != 'N.I.'].groupby(['sigla']).count()[['created_at']].reset_index())
df_estados_publicacoes.rename(columns = {'created_at':'publicacoes'}, inplace = True)
geojson = json.load(open('brazil_geo.json'))
In [23]:
plt.offline.init_notebook_mode()
fig = px.choropleth_mapbox(
 df_estados_publicacoes, #soybean database
 locations = "sigla", #define the limits on the map/geography
 geojson = geojson, #shape information
 color = "publicacoes", #defining the color of the scale through the database
 hover_name = "sigla", #the information in the box
 hover_data = ["publicacoes"],
 title = "Publicações por Estado", #title of the map
 mapbox_style = "carto-positron", #defining a new map style 
 center={"lat":-14, "lon": -55},#define the limits that will be plotted
 zoom = 3, #map view size
 opacity = 0.5, #opacity of the map color, to appear the background,
 color_continuous_scale="Viridis",
)

fig.update_geos(fitbounds = "locations", visible = False)
fig.update_layout(height=800)
fig.show()

Podemos também verificar o número de publicações por cidade, mas para facilitar a visualização, vamos visualizar apenas das 10 cidades com mais publicações:

In [24]:
df.loc[df['cidade'] != 'N.I.'].groupby(['cidade']).count()['created_at'].sort_values(ascending=False).head(10).plot(figsize=(15,5), kind="bar", xlabel='Cidade', ylabel='Nº de publicações')
Out[24]:
<AxesSubplot:xlabel='Cidade', ylabel='Nº de publicações'>

Voltar ao Início
Voltar para Coleta e tratamento de dados